// 版权所有2009 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package walk

import (
	"cmd/compile/internal/base"
	"cmd/compile/internal/ir"
	"cmd/compile/internal/typecheck"
	"cmd/compile/internal/types"
	"cmd/internal/src"
)

// directClosureCall将函数文本的直接调用重写为
// 一个普通函数调用，其中闭包变量作为参数传递。
// 这样可以避免分配闭包对象。
// 
// ，以下调用：
// 
// func（a int）{
// println（byval）
// byref++
// （42）
// 
// 变成：
// 
// func（byval int，&byref int，a int）{
func directClosureCall(n *ir.CallExpr) {
	clo := n.X.(*ir.ClosureExpr)
	clofn := clo.Func

	if ir.IsTrivialClosure(clo) {
	}

	// 我们将在输入参数之前插入捕获的变量。
	var params []*types.Field
	var decls []*ir.Name
	for _, v := range clofn.ClosureVars {
		if !v.Byval() {
			// 如果T类型的v是通过引用捕获的，
			// 我们引入函数param&v*T 
			// 并且v仍然是带&v heapddr的PAUTOHEAP 
			// （访问将隐式解除&v）。

			addr := ir.NewNameAt(clofn.Pos(), typecheck.Lookup("&"+v.Sym().Name))
			addr.Curfn = clofn
			addr.SetType(types.NewPtr(v.Type()))
			v.Heapaddr = addr
			v = addr
		}

		v.Class = ir.PPARAM
		decls = append(decls, v)

		fld := types.NewField(src.NoXPos, v.Sym(), v.Type())
		fld.Nname = v
		params = append(params, fld)
	}

	// f是实际函数的名称。
	f := clofn.Nname
	typ := f.Type()

	// 创建带有参数的新函数类型，
	// 然后更新类型和声明。
	typ = types.NewSignature(typ.Pkg(), nil, nil, append(params, typ.Params().FieldSlice()...), typ.Results().FieldSlice())
	f.SetType(typ)
	clofn.Dcl = append(decls, clofn.Dcl...)

	// 重写呼叫。
	n.X = f
	n.Args.Prepend(closureArgs(clo)...)

	// 更新调用表达式的类型。我们需要这样做，因为typecheck给了它occlosure 
	// 节点的结果类型，但我们只重写了ONAME节点的类型。从逻辑上讲，它们是相同的，但堆栈偏移量可能会改变。
	if typ.NumResults() == 1 {
		n.SetType(typ.Results().Field(0).Type)
	} else {
		n.SetType(typ.Results())
	}

	// 添加到enqueueFunc的闭包。它不再是一个合适的
	// 闭包，但我们可能已经在
	// 函数列表中跳过了它，作为一个非平凡的闭包，所以这只是
	// 确保了它的编译。
	ir.CurFunc.Closures = append(ir.CurFunc.Closures, clofn)
}

func walkClosure(clo *ir.ClosureExpr, init *ir.Nodes) ir.Node {
	clofn := clo.Func

	// 如果没有闭包变量，就不用麻烦包装了。
	if ir.IsTrivialClosure(clo) {
		if base.Debug.Closure > 0 {
			base.WarnfAt(clo.Pos(), "closure converted to global")
		}
		return clofn.Nname
	}

	// 闭包不是琐碎的或直接调用的，所以它将保持闭包状态。
	ir.ClosureDebugRuntimeCheck(clo)
	clofn.SetNeedctxt(true)

	// 如果闭包表达式出现在复合
	// 文本初始化中，则可能会被多次遍历（例如，请参见问题#49029）。
	// 
	// 不要将闭包函数多次添加到编译队列中，因为当
	// 将函数编译两次会导致ICE。
	if !clofn.Walked() {
		clofn.SetWalked(true)
		ir.CurFunc.Closures = append(ir.CurFunc.Closures, clofn)
	}

	typ := typecheck.ClosureType(clo)

	clos := ir.NewCompLitExpr(base.Pos, ir.OCOMPLIT, ir.TypeNode(typ), nil)
	clos.SetEsc(clo.Esc())
	clos.List = append([]ir.Node{ir.NewUnaryExpr(base.Pos, ir.OCFUNC, clofn.Nname)}, closureArgs(clo)...)
	for i, value := range clos.List {
		clos.List[i] = ir.NewStructKeyExpr(base.Pos, typ.Field(i), value)
	}

	addr := typecheck.NodAddr(clos)
	addr.SetEsc(clo.Esc())

	// 强制类型从*struct转换为func类型。
	cfn := typecheck.ConvNop(addr, clo.Type())

	// 要使用的非逃逸温度，如果有。
	if x := clo.Prealloc; x != nil {
		if !types.Identical(typ, x.Type()) {
			panic("closure type does not match order's assigned type")
		}
		addr.Prealloc = x
		clo.Prealloc = nil
	}

	return walkExpr(cfn, init)
}

// closureArgs返回一段表达式，用于初始化给定闭包的自由变量。它们与clo中的变量一一对应。Func。ClosureVars，并且将是
// ONAME节点（如果变量是通过值捕获的）或ONAME节点的
// OADDR（如果不是）。
func closureArgs(clo *ir.ClosureExpr) []ir.Node {
	fn := clo.Func

	args := make([]ir.Node, len(fn.ClosureVars))
	for i, v := range fn.ClosureVars {
		var outer ir.Node
		outer = v.Outer
		if !v.Byval() {
			outer = typecheck.NodAddrAt(fn.Pos(), outer)
		}
		args[i] = typecheck.Expr(outer)
	}
	return args
}

func walkMethodValue(n *ir.SelectorExpr, init *ir.Nodes) ir.Node {
	// 以复合文字的形式创建闭包。对于接收器（x）类型为T的x.M，生成的代码如下所示：
	// 
	// clos=&struct{F uintptpr；rt}{T.M·F，x}
	// 
	// 。

	if n.X.Type().IsInterface() {
		// 现在在nil接口上为方法触发死机。
		// 否则它会发生在包装器中，令人困惑。
		n.X = cheapExpr(n.X, init)
		n.X = walkExpr(n.X, nil)

		tab := ir.NewUnaryExpr(base.Pos, ir.OITAB, n.X)
		check := ir.NewUnaryExpr(base.Pos, ir.OCHECKNIL, tab)
		init.Append(typecheck.Stmt(check))
	}

	typ := typecheck.MethodValueType(n)

	clos := ir.NewCompLitExpr(base.Pos, ir.OCOMPLIT, ir.TypeNode(typ), nil)
	clos.SetEsc(n.Esc())
	clos.List = []ir.Node{ir.NewUnaryExpr(base.Pos, ir.OCFUNC, methodValueWrapper(n)), n.X}

	addr := typecheck.NodAddr(clos)
	addr.SetEsc(n.Esc())

	// 强制类型从*struct转换为func类型。
	cfn := typecheck.ConvNop(addr, n.Type())

	// 要使用的非逃逸温度，如果有。
	if x := n.Prealloc; x != nil {
		if !types.Identical(typ, x.Type()) {
			panic("partial call type does not match order's assigned type")
		}
		addr.Prealloc = x
		n.Prealloc = nil
	}

	return walkExpr(cfn, init)
}

// methodValueWrapper返回表示给定方法值所需的
// wrapper函数（*-fm）的ONAME节点。如果尚未创建
// 包装函数，则会创建它，并将
// 添加到typecheck中。目标德克斯。
func methodValueWrapper(dot *ir.SelectorExpr) *ir.Name {
	if dot.Op() != ir.OMETHVALUE {
		base.Fatalf("methodValueWrapper: unexpected %v (%v)", dot, dot.Op())
	}

	t0 := dot.Type()
	meth := dot.Sel
	rcvrtype := dot.X.Type()
	sym := ir.MethodSymSuffix(rcvrtype, meth, "-fm")

	if sym.Uniq() {
		return sym.Def.(*ir.Name)
	}
	sym.SetUniq(true)

	if base.Debug.Unified != 0 && base.Debug.UnifiedQuirks == 0 {
		base.FatalfAt(dot.Pos(), "missing wrapper for %v", meth)
	}

	savecurfn := ir.CurFunc
	saveLineNo := base.Pos
	ir.CurFunc = nil

	base.Pos = base.AutogeneratedPos

	tfn := ir.NewFuncType(base.Pos, nil,
		typecheck.NewFuncParams(t0.Params(), true),
		typecheck.NewFuncParams(t0.Results(), false))

	fn := typecheck.DeclFunc(sym, tfn)
	fn.SetDupok(true)
	fn.SetWrapper(true)

	// 声明并初始化变量保持接收器。
	ptr := ir.NewHiddenParam(base.Pos, fn, typecheck.Lookup(".this"), rcvrtype)

	call := ir.NewCallExpr(base.Pos, ir.OCALL, ir.NewSelectorExpr(base.Pos, ir.OXDOT, ptr, meth), nil)
	call.Args = ir.ParamNames(tfn.Type())
	call.IsDDD = tfn.Type().IsVariadic()

	var body ir.Node = call
	if t0.NumResults() != 0 {
		ret := ir.NewReturnStmt(base.Pos, nil)
		ret.Results = []ir.Node{call}
		body = ret
	}

	fn.Body = []ir.Node{body}
	typecheck.FinishFuncBody()

	typecheck.Func(fn)
	// 需要对刚刚生成的包装的正文进行类型检查。
	// typecheckslice（）要求在处理ORETURN时设置Curfn。
	ir.CurFunc = fn
	typecheck.Stmts(fn.Body)
	sym.Def = fn.Nname
	typecheck.Target.Decls = append(typecheck.Target.Decls, fn)
	ir.CurFunc = savecurfn
	base.Pos = saveLineNo

	return fn.Nname
}
